package htmlCanvas;

import context.*;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
//import java.io.InputStream;
import java.io.Reader;

import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Image;

import util.Color;
import xml.MiniXmlParser;



public class HtmlParser implements Debug.Constants
{ private static final boolean DEBUG=true;//false;//true; // Overriding Debug.Constants.DEBUG

  private static final int foregroundColor=Color.black;

  /**
  * The list of all HTML components in the current document
  */
  private Vector items = new Vector();
  public Vector getItems() { return(items); }
  
//  private HtmlItem currentItem;
//  public int getMaxPageHeight() { return(currentItem.ypos); }
  
  private HtmlItem lastItem;
  public int getMaxPageHeight() { return(lastItem.ypos); }

  /** The screen left margin */
  private int leftMargin = 0;
  /** The screen top margin */
  private int topMargin = 0;
  /** Interline padding */
  private int lineSpacing = 0;

  private String linkTarget = null;
  private String imageName = null;

  private String currentURL = "http://nohost.com";
  public String getCurrentURL() { return(currentURL); }

  private int xpos;
  private int ypos;

  private int maxHeight = 0;

  // Make this a separate param, to allow for border widths
  private int lineWidth;
  private int currentLine = 0;
  
  
  // *************************************************************************
  // *** State variables
  // *************************************************************************
  private boolean isBold = false;
  private boolean isItalic = false;

  private boolean isLink = false;
  private boolean inImage = false;

  private int currentColor=foregroundColor;
  private int prevColor = currentColor;
  private Font currentFont;

  private boolean preformat = false; // preserve whitespace and newlines
  private boolean center = false; // display text centered on the screen?
  private boolean stopCenter = false;
  private boolean forceNewline = false;
  private boolean htmlParagraph = false; // was the last newline a paragraph break?

  
  
  public HtmlParser(int lineWidth)
  { this.lineWidth=lineWidth;
	  
  }

//  public void parseXHTML(InputStream inputStream,String currentURL)
  public void parseXHTML(Reader reader,String currentURL)
  { this.currentURL=currentURL;
    if(DEBUG) Debug.println("HtmlParser.parseXHTML");
//  if(DEBUG) Debug.println("HtmlParser.parseXHTML("+text+")");
    ypos = 0;
    xpos = 0;
    maxHeight = 0;
    items.removeAllElements();
    isBold = false;
  	isItalic = false;
    isLink = false;
    inImage=false;
    currentColor=foregroundColor;
  	prevColor = currentColor;
    currentFont=Font.getDefaultFont();
  	preformat = false; // preserve whitespace and newlines
  	center = false; // display text centered on the screen?
  	stopCenter = false;
  	forceNewline = false;
  	htmlParagraph = false; // was the last newline a paragraph break  
    
    try
    {
    	
//      MiniXmlParser parser=new MiniXmlParser(inputStream);
      MiniXmlParser parser=new MiniXmlParser(reader);
      parser.setRelaxed(true); // Aksepter div. errors
//      int eventType = parser.getType();
//      while(eventType!=MiniXmlParser.END_DOCUMENT)
          WHILE:while(true)
          { int type=parser.getNextToken();
        	switch(type)
      {
//      if(eventType==XmlPullParser.START_DOCUMENT)
//      { System.out.println("Start document"); }
//      else if(eventType == XmlPullParser.END_DOCUMENT)
//      { System.out.println("End document"); }
//      else
        	
        	
//        if(eventType == MiniXmlParser.START_TAG)
        case MiniXmlParser.START_TAG:
        { String name=parser.getName().toLowerCase();
//          Hashtable attributes=new Hashtable();
//          int i=parser.getAttributeCount();
//          for(int j=0;j<i;j++)
//          { String attr=parser.getAttributeName(j).toLowerCase();
//            String value=parser.getAttributeValue(j);
//            attributes.put(attr,value);
//          }
          Hashtable attributes=parser.getAttributes();
          if(DEBUG) Debug.println("GOT: "+editTag(START,name,attributes));
          treatStartTag(name,attributes);
        } break;
//        else if(eventType == MiniXmlParser.TEXT)
//        { String text=parser.getText();
//          if(DEBUG) Debug.println("GOT: TEXT\""+text+'"');
//          addText(text);
//        }
//        else if(eventType == MiniXmlParser.END_TAG)
        case MiniXmlParser.END_TAG:
        { String name=parser.getName().toLowerCase();
          if(DEBUG) Debug.println("GOT: "+editTag(END,name,null));
          
          { String text=parser.getText();
            if(text!=null)
            { if(DEBUG) Debug.println("GOT: TEXT\""+text+'"');
              addText(text);
            }
          }
          
          treatEndTag(name);
        } break;
//        eventType = parser.next();
      
        case MiniXmlParser.END_OF_FILE:
             { if(LOG_XML) Debug.log("XMPPStreamReader.parse","<<  END OF FILE");
               break WHILE;
             }
      }
    }   
//    ListItems(); // DEBUGGING
      
      
    }
    catch(Exception e) { Debug.EXCEPTION("XMLStreamReader'run",e); }
//    finally { try{inputStream.close();}catch(Exception e){} }
    finally { try{reader.close();}catch(Exception e){} }
  }

  private void treatStartTag(String name,Hashtable attributes)
  { treatTag(name,attributes,false); }

  private void treatEndTag(String name)
  { treatTag(name,null,true); }

  private void treatTag(String name,Hashtable attributes,boolean isClosingTag)
  { String element=name.toLowerCase();
//    if(DEBUG) Debug.println("HtmlParser.treatTag: "+element);
      
    if("br".equals(element)) forceNewline=true;
    else if("li".equals(element)) { if(!isClosingTag) forceNewline=true; }
    else if("p".equals(element))
    { forceNewline=true; htmlParagraph=true; }
    else if("strong".equals(element) || "b".equals(element)) // bold
    { if(isClosingTag) isBold = false;
      else             isBold = true;
    }
    else if("a".equals(element)) // hyperlink
    { if(isClosingTag) isLink = false;
      else
      { // Look for the HREF target value. 
        isLink = true;
        linkTarget=(String)attributes.get("href");
      }
    }
    else if("pre".equals(element))
    { if(isClosingTag) preformat = false;
      else             preformat = true;
    }
    else if("center".equals(element))
    { forceNewline = true;
      if(isClosingTag) stopCenter = true;
      else             center = true;
    }
    else if("font".equals(element))
    { if(isClosingTag) currentColor = prevColor;
      else
      { // Look for the COLOR target value. 
        // Double quotes required: <FONT color="#RRGGBB">
//      if(DEBUG) Debug.println("SET TEXT (FONT-1)");
        try
        {
          String colorVal=(String)attributes.get("color");
          int r,g,b;
          r = Integer.parseInt(colorVal.substring(1,3), 16);
          g = Integer.parseInt(colorVal.substring(3,5), 16);
          b = Integer.parseInt(colorVal.substring(5,7), 16);
          prevColor = currentColor;
          currentColor=Color.rgb(r,g,b);
        } catch(Exception e) {}  
//            if(DEBUG) Debug.println("SET TEXT (FONT-2)");
      }
    }
    else if("img".equals(element))
    { // Image tag: <img src="http://foo/bar.gif">ignore text</img>
      if(isClosingTag) inImage=false;
      else
      { inImage=true;
        imageName=(String)attributes.get("src");
        addImageItem();
      }
    }
    else if("i".equals(element) || "em".equals(element))
    { // italic
      if(isClosingTag) isItalic = false;
      else             isItalic = true;
    }
    // Adjust fonts
    { int face  = Font.FACE_SYSTEM;
      int style = Font.STYLE_PLAIN;
      int size  = Font.SIZE_MEDIUM;
          
      if(isBold&&isItalic) style = Font.STYLE_BOLD|Font.STYLE_ITALIC;
      else if(isBold)      style = Font.STYLE_BOLD;
      else if(isItalic)    style = Font.STYLE_ITALIC;
      else                 style = Font.STYLE_PLAIN;
          
      currentFont = Font.getFont(face,style,size);
    } 
    
    
  }

  private void addImageItem()
  {
	Image image=getImage(imageName);
	// If this image goes past the right screen margin, then 
	// wrap it to the next line.
	if((xpos + image.getWidth()) > lineWidth) forceNewline=true;  
	  
    if(forceNewline) newline();
    HtmlItem item=new HtmlItem(xpos,ypos,currentFont,currentColor,currentLine);
    item.setImage(image);
    addItem(item);
    if(DEBUG) Debug.println("ADDED IMAGE-ITEM: "+item);
  }

  private void addText(String text)
  { 
	if(DEBUG) Debug.println("HtmlParser.addText: \""+text+'"');
    HtmlTokenizer tokenizer=new HtmlTokenizer(text);
    String token=tokenizer.next();
    while(token.length()>0)
    { addTextItem(token+' ');
      token=tokenizer.next();
    }
  }
  
  private void addTextItem(String text)
  { // It's a text item
	//  if(inImage) ????
	if(DEBUG) Debug.println("HtmlParser.addTextItem: \""+text+'"');
	if(DEBUG) Debug.println("HtmlParser.addTextItem: xpos="+xpos);
	if(DEBUG) Debug.println("HtmlParser.addTextItem: lineWidth="+lineWidth);
	String rest=null;

    int textWidth = currentFont.stringWidth(text);
	if((xpos + textWidth) > lineWidth) forceNewline=true;
	
    if(forceNewline) newline();
	if(DEBUG) Debug.println("HtmlParser.addTextItem: textWidth="+textWidth);
    boolean overrun = ((xpos + textWidth) > lineWidth);
    if(overrun)
    { 
      if(DEBUG) Debug.println("SPLIT: text=\""+text+'"');
      L:for(int p=text.length()-1;p>0;p--)
      {
        String head=text.substring(0,p);
        String tail=text.substring(p);
//        if(DEBUG) Debug.println("Try split \""+text+"\" ==> head=\""+head+"\", tail=\""+tail+'"');
        int headWidth = currentFont.stringWidth(head);
        boolean headOverrun=((xpos + headWidth) > lineWidth);
//        if(DEBUG) Debug.println("headOverrun="+headOverrun);
        if(!headOverrun)
        { text=head;
          rest=tail;
          if(DEBUG) Debug.println("SPLIT: head=\""+head+'"');
          if(DEBUG) Debug.println("SPLIT: tail=\""+tail+'"');
          break L;
        }
      }
    }
    
    HtmlItem item=new HtmlItem(xpos,ypos,currentFont,currentColor,currentLine);
    item.setText(text);
    addItem(item);
    if(DEBUG) Debug.println("ADDED TEXT-ITEM: "+item);
     
    if(rest!=null) { forceNewline=true; addTextItem(rest); } // Recursive call
  }
  
  private void newline()
  { maxHeight = 0;
    // If we are centering text, go back and add an offset to items in the current line
    if(center) centerLineItems(xpos,lineWidth,currentLine);
    // a /CENTER tag was encountered, so stop centering 
    if(stopCenter) { center=false; stopCenter=false; }
    xpos=leftMargin;
    ypos=ypos+lineSpacing;
    currentLine++;
    if(htmlParagraph)
    { ypos += currentFont.getHeight()/2; // add an extra blank line for a P tag
      htmlParagraph = false;
    }
    forceNewline=false;
  }

  private void addItem(HtmlItem item)
  { if(isLink) item.setLink(linkTarget);
    adjustItemYPositions(item);
    items.addElement(item);
    xpos += item.getWidth();
    lastItem=item;
  }
  
  // ******************************************************************
  // *** METHOD: getImage
  // ******************************************************************
  /**
  * Url example: "http://myhost.com/test.png"
  */
  Image getImage(String url)
  {
    if(DEBUG) Debug.println("HtmlCanvas.getImage("+url+")");
    // If it doesn't have a ":",assume no protocol, and 
    // prepend the current URL with a "/".
    if(url.indexOf(':') == -1)
    { String currentURLBase = currentURL.substring(0,currentURL.lastIndexOf('/')+1);
      url = currentURLBase + url;
    }
    
    Image image=Util.getImage(url);
    return(image);
  }


  // ******************************************************************
  // *** METHOD: adjustItemYPositions
  // ******************************************************************
  /** When an element has been added to a line of items,
  * it's height may be higher than the current max line height.
  * If so, all the items in the line need to have their
  * ypos pushed downwards to give enough vertical space
  * in the line to hold the new item.
  *
  * Modifies item's ypos in the current line,
  * and also sets ypos and maxHeight to new values.
  */
  void adjustItemYPositions (HtmlItem currentItem)
  {
//    if(DEBUG) Debug.println("HtmlCanvas.adjustItemYPositions("+currentItem+")");
    int deltaY = currentItem.getHeight() - maxHeight;

    // If this item is taller than the current 
    // max height of the items in the line, then
    // push other elements on this line downwards
    // by image-height - (current line's MaxHeight)
    if(deltaY > 0)
    { ypos += deltaY;
      for(int i = 0; i < items.size()-1; i++)
      { HtmlItem item = (HtmlItem) items.elementAt(i);
        if(item.getLineNumber() == currentLine) item.ypos += deltaY;
      }
      maxHeight = currentItem.getHeight();
      currentItem.ypos = ypos;
    }
  }

  // ******************************************************************
  // *** METHOD: centerLineItems
  // ******************************************************************
  void centerLineItems(int itemTotalWidth, int lineWidth, int currentLine)
  {
//    if(DEBUG) Debug.println("HtmlCanvas.centerLineItems("+itemTotalWidth+','+lineWidth+','+currentLine+")");
    int xoffset = (lineWidth - itemTotalWidth) / 2;
    for(int i = 0; i < items.size(); i++)
    { HtmlItem item = (HtmlItem) items.elementAt(i);
      if(item.getLineNumber()==currentLine) item.xpos += xoffset;
    }
  }


  // *** DEBUGGING
  
  private static final int START=0;
  private static final int END=1;
  private static final int EMPTY=2;
  
  private String editTag(int type,String name,Hashtable attributes)
  { StringBuffer buf=new StringBuffer();
	buf.append('<');
    if(type==END) buf.append('/');
    buf.append(name);
    if(type!=END && attributes!=null) appendAttributes(buf,attributes);
    if(type==EMPTY) buf.append('/');
    buf.append('>');
    return(buf.toString());
  }

  private void appendAttributes(StringBuffer buf,Hashtable attributes)
  { Enumeration e=attributes.keys();
    while(e.hasMoreElements())
    { String key=(String)e.nextElement();
      String value=(String)attributes.get(key);
      buf.append(' ');
      buf.append(key);
      buf.append("=\"");
      buf.append(value);
      buf.append('\"');
    }
  }

  
  // ******************************************************************
  // *** METHOD: ListItems -- DEBUGGING
  // ******************************************************************
  public void ListItems()
  { if(DEBUG) Debug.println("HtmlCanvas.ListItems()");
    // loop over the content items, listing them
    for(int i = 0; i < items.size(); i++)
    { HtmlItem item = (HtmlItem) items.elementAt(i);
      if(DEBUG) Debug.println(""+i+": "+item);
    }
  }  
  
}
